package com.edgar.plugin.pm;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Looper;
import android.os.Process;

import com.edgar.plugin.PluginManager;
import com.edgar.plugin.hooks.PluginContextImpl;
import com.edgar.plugin.utils.ClassUtils;
import com.edgar.plugin.utils.DexUtils;
import com.edgar.plugin.utils.MethodUtils;
import com.edgar.plugin.utils.NativeLibraryUtils;
import com.edgar.plugin.utils.Utils;

import java.io.File;
import java.util.Collections;
import java.util.List;

import dalvik.system.DexClassLoader;

import static com.edgar.plugin.pm.PluginDirHelper.DIR_PLUGIN_APK;
import static com.edgar.plugin.pm.PluginDirHelper.DIR_PLUGIN_LIB;

/**
 * Created by Edgar on 2018/2/9.
 */
public class Plugin {

    private Context hostContext;
    private Context pluginContext;
    private String packageName;
    private int versionCode;
    private String versionName;
    private List<PackageParser.Activity> activities;
    private List<PackageParser.Service> services;
    private List<PackageParser.Provider> providers;
    private List<PackageParser.Activity> receivers;
    private List<PackageParser.Permission> permissions;
    private List<PackageParser.Instrumentation> instrumentations;
    private List<String> requestedPermissions;
    private String[] splitNames;
    public int[] splitRevisionCodes;
    public int baseRevisionCode;
    public String sharedUserId;
    public int sharedUserLabel;
    private File pluginPackageDir;
    private File dataDir;
    private File sourceDir;
    private File nativeLibraryDir;
    private ClassLoader mClassLoader;
    private Resources mResources;
    private Looper mMainLooper;
    private ApplicationInfo mApplicationInfo;

    public Plugin(PluginManager pluginManager, PackageParser.Package pkg, File apkFile){
        hostContext = pluginManager.getHostContext();
        mMainLooper = pluginManager.getMainLooper();
        pluginContext = new PluginContextImpl(this);
        packageName = pkg.packageName;
        versionCode = pkg.mVersionCode;
        versionName = pkg.mVersionName;
        activities = Collections.unmodifiableList(pkg.activities);
        services = Collections.unmodifiableList(pkg.services);
        providers = Collections.unmodifiableList(pkg.providers);
        receivers = Collections.unmodifiableList(pkg.receivers);
        baseRevisionCode = pkg.baseRevisionCode;
        splitNames = pkg.splitNames;
        splitRevisionCodes = pkg.splitRevisionCodes;
        sharedUserId = pkg.mSharedUserId;
        sharedUserLabel = pkg.mSharedUserLabel;
        permissions = Collections.unmodifiableList(pkg.permissions);
        requestedPermissions = Collections.unmodifiableList(pkg.requestedPermissions);
        instrumentations = pkg.instrumentation;
        pluginPackageDir = dataDir = PluginDirHelper.makePluginPackageDirectory(hostContext,pkg.packageName);
        sourceDir = new File(pluginPackageDir,DIR_PLUGIN_APK+File.separator+"base.apk");
        nativeLibraryDir = new File(pluginPackageDir,DIR_PLUGIN_LIB);
        mApplicationInfo = pkg.applicationInfo;
        mApplicationInfo.uid = Process.myUid();
        setApplicationPath();
        Utils.copyFile(apkFile, sourceDir);
        NativeLibraryUtils.copyNativeLibrary(hostContext,apkFile, nativeLibraryDir);
        mClassLoader = createClassLoader(mApplicationInfo, hostContext.getClassLoader());
        mResources = createResource(hostContext, apkFile.getAbsolutePath());
        DexUtils.installDex(hostContext, (DexClassLoader) mClassLoader);
        System.out.println("Plugin theme:"+mApplicationInfo.theme);
    }

    private static Resources createResource(Context context,String apkPath){
        AssetManager assetManager = ClassUtils.newInstance(AssetManager.class);
        MethodUtils.invokeMethod(assetManager,"addAssetPath",new Class[]{String.class},apkPath);
        Resources hostResources = context.getResources();
        return new Resources(assetManager,hostResources.getDisplayMetrics(),hostResources.getConfiguration());
    }

    private ClassLoader createClassLoader(ApplicationInfo applicationInfo,ClassLoader parent){
        File optimizedDirectory = new File(pluginPackageDir,"code_cache");
        if (!optimizedDirectory.exists()){
            optimizedDirectory.mkdirs();
        }
        return new DexClassLoader(applicationInfo.sourceDir,
                optimizedDirectory.getAbsolutePath(),applicationInfo.nativeLibraryDir,parent);
    }

    private void setApplicationPath(){
        this.mApplicationInfo.dataDir = dataDir.getAbsolutePath();
        this.mApplicationInfo.sourceDir = sourceDir.getAbsolutePath();
        this.mApplicationInfo.nativeLibraryDir = nativeLibraryDir.getAbsolutePath();
    }

    public Context getPluginContext(){
        return pluginContext;
    }

    public Resources.Theme getTheme(){
        return mResources.newTheme();
    }

    public Resources getResources() {
        return mResources;
    }

    public Looper getMainLooper() {
        return mMainLooper;
    }

    public ClassLoader getClassLoader() {
        return mClassLoader;
    }

    public File getPluginPackageDir() {
        return pluginPackageDir;
    }

    public List<PackageParser.Activity> getActivities() {
        return activities;
    }

    public Context getHostContext() {
        return hostContext;
    }

    public ApplicationInfo getApplicationInfo() {
        return mApplicationInfo;
    }

    public String getPackageName() {
        return packageName;
    }

    public int getVersionCode() {
        return versionCode;
    }

    public String getVersionName() {
        return versionName;
    }

    public List<PackageParser.Service> getServices() {
        return services;
    }

    public List<PackageParser.Provider> getProviders() {
        return providers;
    }

    public List<PackageParser.Activity> getReceivers() {
        return receivers;
    }

    public List<PackageParser.Permission> getPermissions() {
        return permissions;
    }

    public List<PackageParser.Instrumentation> getInstrumentations() {
        return instrumentations;
    }

    public File getDataDir() {
        return dataDir;
    }

    public File getSourceDir() {
        return sourceDir;
    }

    public File getNativeLibraryDir() {
        return nativeLibraryDir;
    }

    public List<String> getRequestedPermissions() {
        return requestedPermissions;
    }

    public String[] getSplitNames() {
        return splitNames;
    }

    public int[] getSplitRevisionCodes() {
        return splitRevisionCodes;
    }

    public int getBaseRevisionCode() {
        return baseRevisionCode;
    }

    public String getSharedUserId() {
        return sharedUserId;
    }

    public int getSharedUserLabel() {
        return sharedUserLabel;
    }
}